package com.xvyy.tingshu.user.service.impl;

import java.util.Date;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.xvyy.tingshu.album.client.AlbumInfoFeignClient;
import com.xvyy.tingshu.common.constant.PublicConstant;
import com.xvyy.tingshu.common.constant.RedisConstant;
import com.xvyy.tingshu.common.constant.SystemConstant;
import com.xvyy.tingshu.common.execption.TsException;
import com.xvyy.tingshu.common.rabbit.constant.MqConst;
import com.xvyy.tingshu.common.rabbit.service.RabbitService;
import com.xvyy.tingshu.common.result.Result;
import com.xvyy.tingshu.common.result.ResultCodeEnum;
import com.xvyy.tingshu.common.util.AuthContextHolder;
import com.xvyy.tingshu.common.util.MongoUtil;
import com.xvyy.tingshu.model.user.UserCollect;
import com.xvyy.tingshu.model.user.UserInfo;
import com.xvyy.tingshu.model.user.UserPaidAlbum;
import com.xvyy.tingshu.model.user.UserPaidTrack;
import com.xvyy.tingshu.user.mapper.UserInfoMapper;
import com.xvyy.tingshu.user.mapper.UserPaidAlbumMapper;
import com.xvyy.tingshu.user.mapper.UserPaidTrackMapper;
import com.xvyy.tingshu.user.service.UserInfoService;
import com.xvyy.tingshu.vo.album.TrackListVo;
import com.xvyy.tingshu.vo.album.TrackStatMqVo;
import com.xvyy.tingshu.vo.user.UserCollectVo;
import com.xvyy.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;


import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private WxMaService wxMaService;
    @Autowired
    private RsaSigner rsaSigner;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RabbitService rabbitService;
    @Autowired
    private UserPaidTrackMapper userPaidTrackMapper;
    @Autowired
    private UserPaidAlbumMapper userPaidAlbumMapper;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;


    @Override
    public Map<String, Object> wxLogin(String code) {

        String openId = "";
        // 1. 判断code码是否存在
        if (StringUtils.isEmpty(code)) {
            throw new TsException(201, "code不能为空");
        }

        // 2. 获取微信登录的客户端信息
        WxMaUserService userService = wxMaService.getUserService();
        try {
            WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);
            openId = sessionInfo.getOpenid();//获取用户的唯一标识
            if (StringUtils.isEmpty(openId)) {
                throw new TsException(201, "openId不能为空");
            }
        } catch (WxErrorException e) {
            throw new TsException(201, "微信内部服务异常");
        }

        //从redis缓存中找刷新令牌是否存在
        String refreshToken = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId;
        String fromRedisJwt = stringRedisTemplate.opsForValue().get(refreshToken);
        if (!StringUtils.isEmpty(fromRedisJwt)) {
            //如果 刷新令牌存在，直接返回
            HashMap<String, Object> map = new HashMap<>();
            map.put("token", fromRedisJwt);
            map.put("refreshToken", fromRedisJwt);
            return map;
        }

        // 3. 查询数据库中是否存在该用户
        // 3.1  创建一个查询条件对象
        LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(UserInfo::getWxOpenId, openId);
        UserInfo userInfo = userInfoMapper.selectOne(lambdaQueryWrapper);
        // 3.2  判断用户是否存在，如果不存在，则新增用户
        if (userInfo == null) {
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openId);
            userInfo.setNickname("昵称-test-【" + System.currentTimeMillis() + "】");
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg"); //初始化一个用户的头像
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            userInfo.setGender(0);
            userInfo.setIntro("这个人很懒，什么都没留下...");

            int insert = userInfoMapper.insert(userInfo);
            log.info("注册的用户信息：{}", insert > 0 ? "success" : "fail");

            // 初始化用户信息
            // 采用rabbitMq异步初始化用户信息
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());


        }

        //3.3 自定义登录信息
        // 思路：1、只用uuid 优点：唯一 缺点：身份信息不具有代表性
        // 思路：2、只用jwt.json web token("xxxxxxxx")  优点：唯一 且还具有身份的唯一标识
        //String token = UUID.randomUUID().toString().replaceAll("-", "");
        String jsonWebToken = getJsonWebToken(openId, userInfo.getId());//抽取方法


        //4. 将jwt令牌存储到redis中
        //redis数据类型： String Set ZSet List Hash(k + v)
        //4.1 引入双令牌机制AccessToken + RefreshToken
        String accessToken = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
//        refreshToken = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId;
        //4.2 访问令牌存储到redis中
//        stringRedisTemplate.opsForValue().set(accessToken, jsonWebToken, 10, TimeUnit.MINUTES);
        stringRedisTemplate.opsForValue().set(accessToken, jsonWebToken, 30, TimeUnit.DAYS);
        //4.2 刷新令牌存储到redis中
        stringRedisTemplate.opsForValue().set(refreshToken, jsonWebToken, 30, TimeUnit.DAYS);

        //5. 将自定义登录信息返回给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", jsonWebToken);// token ：前端用的key
        map.put("refreshToken", refreshToken);
        return map;
    }

    private String getJsonWebToken(String openId, Long userId) {
        //3.4 自定义用户信息
        JSONObject payloadMap = new JSONObject();
        payloadMap.put("openId", openId);
        payloadMap.put("userId", userId);
        //3.5 得到jwt对象
        Jwt jwt = JwtHelper.encode(payloadMap.toString(), rsaSigner);
        //3.6 得到jwt对象的token
//        String jsonWebToken = jwt.getEncoded();
        return jwt.getEncoded();
    }

    @Override
    public Map<String, Object> refreshToken() {

        // 1. 获取请求对象中的token
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        // 2. 获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();

        // 3. 从请求头中获取token
//        String refreshToken = request.getHeader("RefreshToken");
        String refreshToken = request.getHeader("token");

        // 4. 用jwt解析token值 (解析并验证签名)
        Jwt jwt = JwtHelper.decodeAndVerify(refreshToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));

        String claims = jwt.getClaims();// 拿到claims
        Map map = JSONObject.parseObject(claims);
        String openId = (String) map.get("openId");
        Long userId = (Long) map.get("userId");

        // 5. 校验refreshToken是否过期
        String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openId;
        String dataFromRedis = stringRedisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(dataFromRedis)) {
            //没有过期生产新的token令牌

            String jsonWebToken = getJsonWebToken(openId, userId);
            String accessToken = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
            //访问令牌存储到redis中
            stringRedisTemplate.opsForValue().set(accessToken, jsonWebToken, 10, TimeUnit.MINUTES);
            //刷新令牌存储到redis中
            stringRedisTemplate.opsForValue().set(refreshToken, jsonWebToken, 30, TimeUnit.DAYS);

            Map<String, Object> hashMap = new HashMap<>();
            hashMap.put("token", jsonWebToken);
            hashMap.put("refreshToken", refreshToken);

        }
        //如果refreshToken过期了就抛出异常 未登录
        throw new TsException(ResultCodeEnum.LOGIN_AUTH);
    }

    @Override
    public UserInfoVo getUserInfoVo(Long userId) {


        // 1. 根据用户id查询用户信息
        UserInfo uerInfo = userInfoMapper.selectById(userId);
        if (uerInfo == null) {
            throw new TsException(201, "用户不存在");
        }

        // 2. 将用户信息拷贝到UserInfoVo对象中
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(uerInfo, userInfoVo);

        return userInfoVo;
    }

    @Override
    public void updateUser(UserInfoVo userInfoVo) {

        // 1. 从认证中心的ThreadLocal中获取用户id
        Long userId = AuthContextHolder.getUserId();

        // 2. 根据用户id查询用户信息
        UserInfo uerInfo = userInfoMapper.selectById(userId);
        if (uerInfo == null) {
            throw new TsException(201, "用户不存在");
        }
        uerInfo.setNickname(userInfoVo.getNickname());
        uerInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        int count = userInfoMapper.updateById(uerInfo);
        if (count == 0) {
            throw new TsException(201, "更新失败");
        }else {
            log.info("更新用户信息成功");
        }
    }

    @Override
    public Map<Long, String> getUserPaidAlbumTrackList(Long userId, Long albumId) {
        // 1. 查询该用户是否购买过专辑下的声音列表
        List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(
                new LambdaQueryWrapper<UserPaidTrack>()
                        .eq(UserPaidTrack::getUserId, userId)
                        .eq(UserPaidTrack::getAlbumId, albumId)
                        .eq(UserPaidTrack::getIsDeleted, 0));
        if (CollectionUtils.isEmpty(userPaidTrackList)) {
            return new HashMap<>();
        }
        // 2. 将查询到的声音列表封装到map中返回
        return userPaidTrackList
                .stream()
                .collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));
    }

    @Override
    public Boolean getUserPaidAlbum(Long userId, Long albumId) {

        UserPaidAlbum userPaidAlbum = userPaidAlbumMapper.selectOne(
                new LambdaQueryWrapper<UserPaidAlbum>()
                        .eq(UserPaidAlbum::getUserId, userId)
                        .eq(UserPaidAlbum::getAlbumId, albumId)
                        .eq(UserPaidAlbum::getIsDeleted, 0));

        return userPaidAlbum != null;
    }

    @Override
    public Boolean collect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        // 1. 构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);
        // 2. 构建查询对象
        Query query = new Query(criteria);

        // 3. 根据条件查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);
        // 4. 判断是否已经收藏
        if (count == 0) {
            // 没有收藏，进行添加操作
            UserCollect userCollect = new UserCollect();
            userCollect.setId(ObjectId.get().toHexString());
            userCollect.setUserId(userId);
            userCollect.setTrackId(trackId);
            userCollect.setCreateTime(new Date());
            // 将数据保存到MongoDB中
            mongoTemplate.save(userCollect, collectionName);
            // 发送mq消息，更新声音的收藏数
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, 1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return true;
        } else {
            // 取消收藏，进行删除操作
            mongoTemplate.remove(query, collectionName);
            // 发送mq消息，更新声音的收藏数
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, -1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return false;
        }
    }

    @Override
    public Boolean isCollect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        // 1. 构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);
        // 2. 构建查询对象
        Query query = new Query(criteria);
        // 3. 根据条件查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        // 4. 根据条件查询数量
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);
        return count > 0;
    }

    @Override
    public Boolean isSubscribe(Long albumId) {
        Long userId = AuthContextHolder.getUserId();
        // 1. 构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId).and("albumId").is(albumId);
        // 2. 构建查询对象
        Query query = new Query(criteria);
        // 3. 根据条件查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);
        return count > 0;
    }

    @Override
    public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> page) {
        Long userId = AuthContextHolder.getUserId();
        // 1. 构建条件对象
        Criteria criteria = Criteria.where("userId").is(userId);
        // 2. 构建查询对象
        Query query = new Query(criteria);
        // 3. 分页和排序
        long pageCurrent = page.getCurrent();
        long pageSize = page.getSize();
        long from = (pageCurrent - 1) * pageSize;
        Sort SortByUpdateTime = Sort.by(Sort.Direction.DESC, "updateTime");
        PageRequest pageRequest = PageRequest.of((int) from, (int) pageSize, SortByUpdateTime);
        // 4. 根据条件查询（查询满足条件的总记录数）
        query.with(pageRequest);
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long total = mongoTemplate.count(query.limit(-1), UserCollect.class, collectionName);
        // 5. 查询数据列表
        List<UserCollect> userCollectList = mongoTemplate.find(query, UserCollect.class, collectionName);
        // 6. 获取声音id集合
        List<Long> collectTrackIdList = userCollectList.stream().map(UserCollect::getTrackId).collect(Collectors.toList());

        // 7. 查询数据库获取到标题以及封面专辑id
        if (!CollectionUtils.isEmpty(collectTrackIdList)) {
            //远调album微服务，获取收藏的声音集合
            Result<List<TrackListVo>> trackListVoResult = albumInfoFeignClient.getTrackListVo(collectTrackIdList);
            List<TrackListVo> trackListVoData = trackListVoResult.getData();
            Assert.notNull(trackListVoData, "声音列表为空");

            // 8. 将查询到的声音列表封装到map中
            Map<Long, TrackListVo> map = trackListVoData.stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));
            List<UserCollectVo> userCollectVoList = userCollectList.stream().map(userCollect -> {
                UserCollectVo userCollectVo = new UserCollectVo();
                userCollectVo.setAlbumId(map.get(userCollect.getTrackId()).getAlbumId());// 专辑id
                userCollectVo.setTrackId(userCollect.getTrackId());// 声音id
                userCollectVo.setCreateTime(userCollect.getCreateTime());// 收藏时间
                userCollectVo.setTrackTitle(map.get(userCollect.getTrackId()).getTrackTitle());// 声音标题
                userCollectVo.setCoverUrl(map.get(userCollect.getTrackId()).getCoverUrl());// 专辑封面
                return userCollectVo;
            }).collect(Collectors.toList());
            return page.setRecords(userCollectVoList).setTotal(total);
        }
        return page;
    }

    @Override
    public UserInfo updateUserVip(UserInfo userInfo) {
        userInfoMapper.update(userInfo,
                new LambdaUpdateWrapper<UserInfo>()
                        .eq(UserInfo::getId, userInfo.getId())
                        .set(UserInfo::getIsVip, 0));
        return userInfo;
    }

    @Override
    public void updateVipExpireStatus() {
        List<UserInfo> userInfoList = userInfoMapper.selectList(
                new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getIsVip, 1)
                        .le(UserInfo::getVipExpireTime, new Date()));

        if (!CollectionUtils.isEmpty(userInfoList)) {
            userInfoList.forEach(userInfo -> {
                userInfo.setIsVip(0);
                userInfoMapper.updateById(userInfo);
            });
        }
    }


    private TrackStatMqVo prepareCollectMsgDto(Long trackId, Integer count) {

        TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
        trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replaceAll("-", ""));
//        trackStatMqVo.setAlbumId(0L);
        trackStatMqVo.setTrackId(trackId);
        trackStatMqVo.setStatType(SystemConstant.TRACK_STAT_COLLECT);
        trackStatMqVo.setCount(count);
        return trackStatMqVo;
    }

}
